﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SilverMap.Editor.Models;
using System.Windows.Media.Imaging;

namespace SilverMap.Editor
{
	public partial class MainPage : UserControl
	{
		private const string GridDetailsFileName = "GridDetails.xml";

		private Tile _selectedTile = null;
		private MapItem _selectedMapItem = null;
		private double _opacity = 1D;
		private double _rotation = 0D;
		private double _scale = 1D;
		private double _scaleOrigin = 1D;
		private double _zIndex = 0D;
		private int _layer = 0;

		private Point _center = new Point();
		private Point _mousePosition = new Point();

		private bool _isScaling = false;
		private bool _isRotating = false;

		private bool _isInSelectMode = false;
		private bool _snapToGrid = false;

		private GridDetail _gridDetail;

		/// <summary>
		/// Gets or sets the x increment to move a selected tile.
		/// </summary>
		public double SelectionMoveX
		{
			get { return (double)GetValue(SelectionMoveXProperty); }
			set { SetValue(SelectionMoveXProperty, value); }
		}
		public static readonly DependencyProperty SelectionMoveXProperty = DependencyProperty.Register("SelectionMoveX", typeof(double), typeof(MainPage), new PropertyMetadata(1.0));

		/// <summary>
		/// Gets or sets the Y increment to move a selected tile.
		/// </summary>
		public double SelectionMoveY
		{
			get { return (double)GetValue(SelectionMoveYProperty); }
			set { SetValue(SelectionMoveYProperty, value); }
		}
		public static readonly DependencyProperty SelectionMoveYProperty = DependencyProperty.Register("SelectionMoveY", typeof(double), typeof(MainPage), new PropertyMetadata(1.0));

		/// <summary>
		/// Get or sets the x increment to move the map while in draw mode.
		/// </summary>
		public double DrawMoveX
		{
			get { return (double)GetValue(DrawMoveXProperty); }
			set { SetValue(DrawMoveXProperty, value); }
		}
		public static readonly DependencyProperty DrawMoveXProperty = DependencyProperty.Register("DrawMoveX", typeof(double), typeof(MainPage), new PropertyMetadata(64.0));

		/// <summary>
		/// Get or sets the y increment to move the map while in draw mode.
		/// </summary>
		public double DrawMoveY
		{
			get { return (double)GetValue(DrawMoveYProperty); }
			set { SetValue(DrawMoveYProperty, value); }
		}
		public static readonly DependencyProperty DrawMoveYProperty = DependencyProperty.Register("DrawMoveY", typeof(double), typeof(MainPage), new PropertyMetadata(64.0));
				
		public MainPage()
		{
			this.Loaded += new RoutedEventHandler(MainPage_Loaded);
			InitializeComponent();
		}

		void MainPage_Loaded(object sender, RoutedEventArgs e)
		{
			App.Current.RootVisual.KeyDown += new KeyEventHandler(RootVisual_KeyDown);
			App.Current.RootVisual.KeyUp += new KeyEventHandler(RootVisual_KeyUp);

			_gridDetail = new GridDetail();
			_gridDetail.Load(GridDetailsFileName);

			var dialog = new TileLoaderWindow();
			dialog.Closed += (o, ea) =>
				{
					if (dialog.DialogResult == true)
					{
						// Successfully loaded or created a tile file, enable the menu control.
						TilesList.ItemsSource = MapManager.Tiles.Values;
						MenuPanel.Visibility = Visibility.Visible;
						Map.GridSize = _gridDetail.GridSize;
						DrawMoveX = Map.GridSize.Width;
						DrawMoveY = Map.GridSize.Height;
					}
				};
			dialog.Show();

		}

		#region Keyboard Event Handlers
		private void RootVisual_KeyDown(object sender, KeyEventArgs e)
		{
			var refreshMap = false;

			// Reset Tile Props
			if (e.Key == Key.N)
			{
				ResetTileProperties();
			}

			// Scale
			if (e.Key == Key.S)
			{
				_isScaling = !_isScaling;
				if (_isScaling)
				{
					EndRotation();
					BeginScale();
				}
				else
					EndScale();
			}

			// Rotate
			if (e.Key == Key.R)
			{
				_isRotating = !_isRotating;
				if (_isRotating)
				{
					EndScale();
					BeginRotation();
				}
				else
					EndRotation();
			}

			// Move Z index of selected item down
			if (e.Key == Key.X)
			{
				if (_selectedMapItem != null)
				{
					_selectedMapItem.Z--;
					if (_selectedMapItem.Z < 0)
						_selectedMapItem.Z = 0;
					refreshMap = true;
					SetStatus(String.Format("Z Index Set to {0}", _selectedMapItem.Z));
				}
			}

			// Move Z index of selected item up
			if (e.Key == Key.Z)
			{
				if (_selectedMapItem != null)
				{
					_selectedMapItem.Z++;
					refreshMap = true;
					SetStatus(String.Format("Z Index Set to {0}", _selectedMapItem.Z));
				}
			}

			// Move Layer Down
			if (e.Key == Key.K)
			{
				_layer--;
				if (_layer < 0) _layer = 0;
				_zIndex = Map.GetMaxZIndex(_layer);
				refreshMap = true;
				LayerLabel.Text = String.Concat("Layer ", _layer);
			}

			// Move Layer Up
			if (e.Key == Key.L)
			{
				_layer++;
				if (_layer > Map.LayerUpperBound)
					Map.AddLayer();
				_zIndex = Map.GetMaxZIndex(_layer);
				refreshMap = true;
				LayerLabel.Text = String.Concat("Layer ", _layer);
			}

			// Delete currently selected item.
			if (e.Key == Key.Delete)
			{
				if (_selectedMapItem != null)
				{
					Map.Items.Remove(_selectedMapItem);
					_selectedMapItem = null;
					SetStatus("Selected Map Item Deleted");
				}
			}

			// Opacity Up
			if (e.Key == Key.O)
			{
				_opacity += 0.01;
				if (_selectedMapItem != null)
				{
					_selectedMapItem.Opacity = _opacity;
					refreshMap = true;
				}
			}
			
			// Opacity Down
			if (e.Key == Key.P)
			{
				_opacity -= 0.01;
				if (_selectedMapItem != null)
				{
					_selectedMapItem.Opacity = _opacity;
					refreshMap = true;
				}
			}

			// Adjust selected tile.
			if (e.Key == Key.Up)
			{
				AdjustSelectedItem(0, -(SelectionMoveY));
				AdjustMapView(0, -(DrawMoveY));
				refreshMap = true;
			}
			if (e.Key == Key.Down)
			{
				AdjustSelectedItem(0, SelectionMoveY);
				AdjustMapView(0, DrawMoveY);
				refreshMap = true;
			}
			if (e.Key == Key.Left)
			{
				AdjustSelectedItem(-(SelectionMoveX), 0);
				AdjustMapView(-(DrawMoveX), 0);
				refreshMap = true;
			}
			if (e.Key == Key.Right)
			{
				AdjustSelectedItem(SelectionMoveX, 0);
				AdjustMapView(DrawMoveX, 0);
				refreshMap = true;
			}

			// Zoom Out
			if (e.Key == Key.PageUp)
			{
				Map.MapScale += 0.01;
				ScaleLabel.Text = String.Format("Map Scale: {0}", Map.MapScale);
			}

			// Zoom In
			if (e.Key == Key.PageDown)
			{
				Map.MapScale -= 0.01;
				ScaleLabel.Text = String.Format("Map Scale: {0}", Map.MapScale);
			}

			if (refreshMap)
				Map.Draw(TimeSpan.Zero);

			e.Handled = true;
		}

		private void RootVisual_KeyUp(object sender, KeyEventArgs e)
		{
		}
		#endregion

		#region Map Event Handlers
		private void Map_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
		{
			if (_selectedTile != null)
			{
				CursorImage.Visibility = Visibility.Visible;

				MoveCursorImage(e.GetPosition(null));
			}
		}

		private void Map_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
		{
			if (_selectedTile != null)
			{
				CursorImage.Visibility = Visibility.Collapsed;
			}
			SetStatus(String.Empty);
		}

		private void Map_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
		{
			_mousePosition = e.GetPosition(null);

			if (_isScaling)
			{
				// Scale
				_scale = _scaleOrigin + (_center.Distance(_mousePosition) * 0.01);
				UpdateCursorImageProperties();
				SetStatus(String.Format("Scale: {0}", _scale));
				if (_selectedMapItem != null)
					_selectedMapItem.Scale = _scale;
			}
			else if (_isRotating)
			{
				// Rotate
				_rotation = -(_center.FindAngle(_mousePosition));
				UpdateCursorImageProperties();
				SetStatus(String.Format("Rotation: {0}", _rotation));
				if (_selectedMapItem != null)
					_selectedMapItem.Rotation = _rotation;
			}
			else
			{
				MoveCursorImage(_mousePosition);
				var p = _mousePosition.Add(Map.Offset);
				SetStatus(String.Format("X: {0}, Y: {1}", p.X, p.Y));
			}
		}

		private void Map_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
			// Focus the map so that keyboard events are not captured by the list box.
			Map.Focus();

			var p = e.GetPosition(null).Add(Map.Offset);
			if (_isInSelectMode)
			{
				_selectedMapItem = Map.SelectItem(p, _layer) as MapItem;
				if (_selectedMapItem == null)
				{
					SetStatus("Map Item was NOT found! Try a different Layer!");
				}
				else
				{
					_selectedTile = null;
					_rotation = _selectedMapItem.Rotation;
					_scale = _selectedMapItem.Scale;
					_opacity = _selectedMapItem.Opacity;
					SetStatus("Map Item Selected");
				}
			}
			else
			{
				if (_selectedTile == null)
					return;

				var x = p.X;
				var y = p.Y;
				if (_snapToGrid)
				{
					x = ((int)(x / Map.GridSize.Width) * Map.GridSize.Width);
					y = ((int)(y / Map.GridSize.Height) * Map.GridSize.Height);
					switch (_gridDetail.Alignment)
					{
						case ItemAlignment.TopLeft:
							// No need to do anything.
							break;
						case ItemAlignment.TopRight:
							x += Map.GridSize.Width - _selectedTile.Width;
							break;
						case ItemAlignment.BottomLeft:
							y += Map.GridSize.Height - _selectedTile.Height;
							break;
						case ItemAlignment.BottomRight:
							x += Map.GridSize.Width - _selectedTile.Width;
							y += Map.GridSize.Height - _selectedTile.Height;
							break;	
					}
				}

				var item = new MapItem
				{
					X = x,
					Y = y,
					Z = _zIndex++,
					Layer = _layer,
					Width = _selectedTile.Width,
					Height = _selectedTile.Height,
					Opacity = _opacity,
					Rotation = _rotation,
					Scale = _scale,
					Tile = _selectedTile
				};
				Map.Items.Add(item);
			}
		}

		private void Map_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
		{
		}
		#endregion

		#region TilesList Event Handlers
		private void TilesList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
		{
			_selectedTile = e.AddedItems[0] as Tile;
			CursorImage.Source = _selectedTile.Image;
		}
		#endregion

		#region Begin/End Rotation and Scale
		private void BeginRotation()
		{
			RotatingLabel.Visibility = Visibility.Visible;
			_center = _mousePosition;
		}

		private void EndRotation()
		{
			_isRotating = false;
			RotatingLabel.Visibility = Visibility.Collapsed;
		}

		private void BeginScale()
		{
			ScalingLabel.Visibility = Visibility.Visible;
			_center = _mousePosition;
		}

		private void EndScale()
		{
			_isScaling = false;
			ScalingLabel.Visibility = Visibility.Collapsed;
		}
		#endregion

		#region Image Cursor Methods
		private void MoveCursorImage(Point p)
		{
			CursorImage.MoveTo(p);
		}

		private void ResetTileProperties()
		{
			_opacity = 1D;
			_rotation = 0D;
			_scale = 1D;
			UpdateCursorImageProperties();
		}

		private void UpdateCursorImageProperties()
		{
			var scaleTransform = CursorImage.Transform<ScaleTransform>();
			scaleTransform.ScaleX = scaleTransform.ScaleY = _scale;

			var rotateTransform = CursorImage.Transform<RotateTransform>();
			rotateTransform.Angle = _rotation;
		}
		#endregion

		#region Adjust Map Item, Adjust Map View
		private void AdjustSelectedItem(double x, double y)
		{
			if (_selectedMapItem == null)
				return;

			_selectedMapItem.X += x;
			_selectedMapItem.Y += y;
		}

		private void AdjustMapView(double x, double y)
		{
			if (_selectedMapItem != null)
				return;

			Map.SetCamera(Map.Center.Add(x, y));
		}
		#endregion

		#region SetStatus
		private void SetStatus(string text)
		{
			this.Dispatcher.BeginInvoke(() =>
				{
					StatusLabel.Text = text;
				});
		}
		#endregion

		#region Menu Button Event Handlers
		private void NewMapButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			Map.Clear();
		}

		private void OpenMapButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			var fdOpen = new OpenFileDialog { Multiselect = false, Filter = "Map XML files|*.xml" };
			if (fdOpen.ShowDialog() == true)
			{
				using (var s = fdOpen.File.OpenRead())
				{
					Map.Load(s, 
						new GetTileCallback((tileId) =>
						{
							return MapManager.Tiles[tileId];
						}));
					_layer = 0;
					_zIndex = Map.GetMaxZIndex(_layer);
				}
				SetStatus("Map loaded successfully!");
			}
		}

		private void SaveMapButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			var fdSave = new SaveFileDialog { DefaultExt = "xml", Filter = "Map XML files|*.xml" };
			if (fdSave.ShowDialog() == true)
			{
				using (var s = fdSave.OpenFile())
				{
					Map.Save(s);
				}
				SetStatus("Map saved successfully!");
			}
		}

		private void SnapToGridButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			_snapToGrid = !_snapToGrid;
			SnapToGridLabel.Visibility = _snapToGrid ? Visibility.Visible : Visibility.Collapsed;
		}

		private void GridLinesButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			Map.IsTileGridVisible = !Map.IsTileGridVisible;
		}

		private void SelectButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			_isInSelectMode = true;
			_selectedTile = null;
			EndRotation();
			EndScale();
		}

		private void DrawButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			_isInSelectMode = false;
			_selectedMapItem = null;
			Map.ClearSelection();
		}

		private void ShortcutsButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			ShortCutsPanel.Visibility = ShortCutsPanel.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
		}

		private void GridDetailsButton_Click(object sender, System.Windows.RoutedEventArgs e)
		{
			_gridDetail.GridSize = Map.GridSize;
			var dialog = new GridDetailsWindow { DataContext = _gridDetail };
			dialog.Closed += (o, args) =>
				{
					this.Dispatcher.BeginInvoke(() =>
						{
							Map.GridSize = _gridDetail.GridSize;
							_gridDetail.Save(GridDetailsFileName);
						});
				};
			dialog.Show();
		}
		#endregion
	}
}
